package com.tf_cut.backend.request;

import org.apache.http.HttpEntity;

import com.tf_cut.backend.request.util.HttpsUtil;
import com.tf_cut.backend.request.util.MybankLogger;
import com.tf_cut.backend.request.util.StringUtils;
import com.tf_cut.backend.request.xml.ObjectXmlParser;
import com.tf_cut.backend.seller.enums.MybankApiExceptionEnum;
import com.tf_cut.backend.util.MybankConstants;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;

public abstract class AbstractMybankClient implements MybankClient {

    private String serverUrl;

    private String format   = MybankConstants.FORMAT_XML;
    private String signType = MybankConstants.SIGN_TYPE_RSA;
    private String charset = MybankConstants.CHARSET_UTF8;

    private int connectTimeout = 3000;
    private int readTimeout    = 15000;
    
    public AbstractMybankClient(String serverUrl, String charset) {
        this.serverUrl = serverUrl;
        this.charset = charset;
    }


    public AbstractMybankClient(String serverUrl) {
        this.serverUrl = serverUrl;
    }
    
    public AbstractMybankClient(String serverUrl, String charset, String signType) {
        this(serverUrl, charset);
        if (!StringUtils.isEmpty(signType)) {
            this.signType = signType;
        }
    }

    public AbstractMybankClient(String serverUrl, String format, String charset, String signType) {
        this(serverUrl, charset);
        if (!StringUtils.isEmpty(format)) {
            this.format = format;
        }
        if (!StringUtils.isEmpty(signType)) {
            this.signType = signType;
        }
    }
    
    @Override
    public <T extends MybankResponse> T execute(MybankUploadRequest<T> request) throws MybankApiException {
        MybankParser<T> parser = new ObjectXmlParser<T>(request.getResponseClass());
        return execute(request, parser);
    }
    
    
    private <T extends MybankResponse> T execute(DefaultRequest<T> request, MybankParser<T> parser) throws MybankApiException {
        long beginTime = System.currentTimeMillis();
        Map<String, Object> rt = new HashMap<String, Object>();
            rt = doPost((MybankUploadRequest<T>) request);

        Map<String, Long> costTimeMap = new HashMap<String, Long>();
        if (rt.containsKey(MybankConstants.PREPARE_TIME)) {
            costTimeMap.put("prepareCostTime", (Long)(rt.get(MybankConstants.PREPARE_TIME)) - beginTime);
            if (rt.containsKey(MybankConstants.REQUEST_TIME)) {
                costTimeMap.put("requestCostTime", (Long)(rt.get(MybankConstants.REQUEST_TIME)) - (Long)(rt.get(MybankConstants.PREPARE_TIME)));
            }
        }
        T tRsp = null;
        try {
            tRsp = parser.parse((String)rt.get(MybankConstants.RSP_CONTENT));
            tRsp.setXmlContent((String)rt.get(MybankConstants.RSP_CONTENT));
            tRsp.setRequestContent((String)rt.get("requestContent"));

            checkResponseSign((String)rt.get(MybankConstants.RSP_CONTENT));

            if (costTimeMap.containsKey(MybankConstants.REQUEST_COST_TIME)) {
                costTimeMap.put("postCostTime", System.currentTimeMillis() - (Long)(rt.get(MybankConstants.REQUEST_TIME)));
            }
        } catch (RuntimeException e) {
            MybankLogger.logBizError((String)rt.get(MybankConstants.RSP_CONTENT), costTimeMap);
            throw new MybankApiException(e);
        } catch (MybankApiException e) {
            MybankLogger.logBizError((String)rt.get(MybankConstants.RSP_CONTENT), costTimeMap);
            throw e;
        }
        MybankLogger.logBizSummary(rt, costTimeMap);
        return tRsp;
    }
    
    
    
    private <T extends MybankResponse> Map<String, Object> doPost(MybankUploadRequest<T> request) throws MybankApiException {
        Map<String, Object> result = new HashMap<String, Object>();
        Map<String, Object> map = null;
        try {
            map = request.getMapByModel();
        }catch (MybankApiException e){
            MybankLogger.logBizError(e);
            throw new MybankApiException(MybankApiExceptionEnum.SDK_SYSTEM_EXCEPTION,e);
        }

        String signString = request.uploadRequestSignString(map);

        try {
            signString = getSigner().webSign(signString, this.charset, this.signType);
        }catch (MybankApiException e){
            MybankLogger.logBizError(e);
            throw new MybankApiException(MybankApiExceptionEnum.SDK_SYSTEM_EXCEPTION,e);
        }

        if (request instanceof MerchantprodMerchantUploadphotoRequest){
            MerchantprodMerchantUploadphotoRequest uploadphotoRequest = (MerchantprodMerchantUploadphotoRequest) request;
            uploadphotoRequest.getUploadphotoRequestModel().setSignature(signString);
        }

        map.put("Signature", signString);
        HttpEntity httpEntity = request.entityBuilder(map);
        ByteArrayOutputStream out = new ByteArrayOutputStream((int) httpEntity.getContentLength());
        try {
            httpEntity.writeTo(out);
            result.put("requestContent", out.toString());
        }catch (IOException e){
            MybankLogger.logBizError(e);
            throw new MybankApiException(MybankApiExceptionEnum.SDK_SYSTEM_EXCEPTION,e);
        }finally{
            try {
                out.close();
            }catch (IOException e){
                MybankLogger.logBizError(e);
                throw new MybankApiException(MybankApiExceptionEnum.SDK_SYSTEM_EXCEPTION,e);
            }
        }
        result.put("prepareTime", System.currentTimeMillis());

        String rsp = null;
        try {
            rsp = HttpsUtil.httpPost(this.serverUrl, httpEntity, this.charset, request.getApiFunction(), request.getApiVersion());
        } catch (MybankApiException e) {
            throw new MybankApiException(MybankApiExceptionEnum.REQUEST_REMOTE_SERVER_ERROR, e);
        }
        result.put("requestTime", System.currentTimeMillis());
        result.put("rspContent", rsp);
        result.put("url", this.serverUrl);
        return result;
    }
    
    private void checkResponseSign(String rspContent) throws MybankApiException {
        boolean checkResult = getSignChecker().check(rspContent, this.charset, this.signType);
        if (!checkResult) {
            throw new MybankApiException(MybankApiExceptionEnum.VERIFY_FAIL);
        }
    }


    

	void setServerUrl(String serverUrl) { this.serverUrl = serverUrl; }

    void setFormat(String format) { this.format = format; }

    void setSignType(String signType) { this.signType = signType; }

    void setCharset(String charset) { this.charset = charset; }

    void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; }

    void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; }

    abstract Signer getSigner();
    
    abstract SignChecker getSignChecker();

}